package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.IpUtil;
import com.atguigu.gmall.common.util.JSONs;
import com.atguigu.gmall.gateway.config.AuthProperties;
import com.atguigu.gmall.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @Author:lyd
 * @Date:2023/8/30 16:29
 * @Version:1.0
 * @Description 配置一个拦截器，对经过网关的请求进行处理
 **/
@Component
@Slf4j
public class UserAuthGlobalFilter implements GlobalFilter {
    @Autowired
    private AuthProperties authProperties;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //匹配路径的工具类
        AntPathMatcher matcher = new AntPathMatcher();
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        String url = request.getURI().toString();
        String ipAddress = IpUtil.getGatwayIpAddress(request);
        log.info("请求进来：路径{}" + path);

        //1、直接放行的路径请求---->静态资源
        long pathCount = authProperties.getPathUrl()
                .stream()
                .filter(item -> matcher.match(item, path))
                .count();
        if (pathCount > 0) {
            return doFilter(exchange, chain, path);
        }
        //2、拒绝的路径请求--->内部路径
        long denyCount = authProperties.getDenyUrl()
                .stream()
                .filter(item -> matcher.match(item, path))
                .count();
        if (denyCount > 0) {
            // 通过发布者的模式，给前端响应数据
            Mono<Void> denyDataMon = getVoidMono(exchange);
            return denyDataMon;
        }
        //3、需要授权的路径请求----->order、pay等
        long authCount = authProperties.getAuthUrl()
                .stream()
                .filter(item -> matcher.match(item, path))
                .count();
        if (authCount > 0) {
            String token = getUserToken(exchange);
            UserInfo userInfo = getLoginUser(exchange, token);
            if (userInfo == null || !ipAddress.equals(userInfo.getDeviceId())) {
                String location = authProperties.getLoginUrl() + "?originUrl=" + url;
                ServerHttpResponse response = exchange.getResponse();
                return sendRedirect(location, response);
            } else {
                return doFilter(exchange, chain, path);
            }
        }

        //4、普通请求，带了token就检查，没带则放行
        String token = getUserToken(exchange);
        if (!StringUtils.hasText(token)) {
            //放行
            return doFilter(exchange, chain, path);
        } else {
            UserInfo userInfo = getLoginUser(exchange, token);
            if (userInfo == null || !ipAddress.equals(userInfo.getDeviceId())) {
                //带了token，但是假的
                String location = authProperties.getLoginUrl() + "?originUrl=" + url;
                ServerHttpResponse response = exchange.getResponse();
                return sendRedirect(location, response);
            } else {
                return doFilter(exchange, chain, path);
            }
        }
    }

    /**
     * 获取用户的信息
     *
     * @param exchange
     * @return
     */
    private UserInfo getLoginUser(ServerWebExchange exchange, String token) {
        //排除token为空的情况
        if (!StringUtils.hasText(token)) {
            return null;
        }
        String jsonToken = redisTemplate.opsForValue().get(RedisConst.LOGIN_USER + token);
        //redis没有对应的token有两种情况
        //1、假的token 2、redis中的token过期
        if (!StringUtils.hasText(jsonToken)) {
            return null;
        }
        //token为正确的值
        UserInfo userInfo = JSONs.jsonToObj(jsonToken, UserInfo.class);
        return userInfo;
    }

    /**
     * 获取用户的token
     *
     * @param exchange
     * @return
     */
    private static String getUserToken(ServerWebExchange exchange) {
        String token = "";
        HttpCookie cookie = exchange.getRequest().getCookies().getFirst("token");
        if (cookie != null) {
            token = cookie.getValue();
        } else {
            token = exchange.getRequest().getHeaders().getFirst("Token");
        }
        return token;
    }

    /**
     * 用户没有传入正确的token，将转发到登录页去登录
     *
     * @param location
     * @param response
     * @return
     */
    private static Mono<Void> sendRedirect(String location, ServerHttpResponse response) {
        //设置重定向的状态码，302
        response.setStatusCode(HttpStatus.FOUND);
        response.getHeaders().set("Location", location);
        log.info("本次重定向需要去的位置：{}" + location);
        //设置新的token，替换原来旧的token，并设置保存时间为0，即立即删除
        ResponseCookie cookieNew = ResponseCookie
                .from("token", "aaa")
                .domain(".gmall.com")
                .maxAge(0)
                .build();
        //添加到cookie里面
        response.addCookie(cookieNew);
        Mono<Void> mono = response.setComplete();
        return mono;
    }

    /**
     * 通过发布者的模式，给前端响应数据
     *
     * @param exchange
     * @return
     */
    private static Mono<Void> getVoidMono(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        //拿到响应给前端的数据
        Result<String> result = Result.build("", ResultCodeEnum.REFUSE_ACCESS);
        String jsonStr = JSONs.toJSONStr(result);
        //获取缓存工厂
        DataBufferFactory factory = response.bufferFactory();
        //通过缓存工厂拿到dataBuffer
        DataBuffer dataBuffer = factory.wrap(jsonStr.getBytes());
        //使用发布者把数据发布出去
        Mono<DataBuffer> respData = Mono.just(dataBuffer);
        //给响应头中设置字符编码，防止中文乱码
        response.getHeaders().set("Content-Type", "application/json;charset=utf-8");
        Mono<Void> denyDataMon = response.writeWith(respData);
        return denyDataMon;
    }

    /**
     * 直接放行的路径，不需要网关的处理
     * exchange.getRequest().getURI().getPath() ----> /js/plugins/jquery.cookie.js
     * exchange.getRequest().getURI().toString()----> http://www.gmall.com/img/brand_07.png
     *
     * @param exchange
     * @param chain
     * @return
     */
    private static Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain, String path) {
        Mono<Void> mono = chain.filter(exchange)
                .doFinally((item) -> {
                    log.info("请求结束：路径{}", path);
                });
        return mono;
    }
}
